]> git.saurik.com Git - apple/security.git/blob - OSX/Keychain Circle Notification/KNAppDelegate.m
Security-57336.10.29.tar.gz
[apple/security.git] / OSX / Keychain Circle Notification / KNAppDelegate.m
1 /*
2 * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25 #import "KNAppDelegate.h"
26 #import "KDSecCircle.h"
27 #import "KDCirclePeer.h"
28 #import "NSDictionary+compactDescription.h"
29 #import <AOSUI/NSImageAdditions.h>
30 #import <AppleSystemInfo/AppleSystemInfo.h>
31 #import <Security/SecFrameworkStrings.h>
32
33 #import <AOSAccounts/MobileMePrefsCoreAEPrivate.h>
34 #import <AOSAccounts/MobileMePrefsCore.h>
35
36 #include <msgtracer_client.h>
37 #include <msgtracer_keys.h>
38 #include <CrashReporterSupport/CrashReporterSupportPrivate.h>
39
40 static const char * const kLaunchLaterXPCName = "com.apple.security.Keychain-Circle-Notification-TICK";
41 static const NSString * const kKickedOutKey = @"KickedOut";
42 static const NSString * const kValidOnlyOutOfCircleKey = @"ValidOnlyOutOfCircle";
43
44
45 @implementation KNAppDelegate
46
47 static NSUserNotificationCenter *appropriateNotificationCenter()
48 {
49 return [NSUserNotificationCenter _centerForIdentifier: @"com.apple.security.keychain-circle-notification"
50 type: _NSUserNotificationCenterTypeSystem];
51 }
52
53
54 - (void) notifyiCloudPreferencesAbout: (NSString *) eventName
55 {
56 if (eventName == nil)
57 return;
58
59 NSString *account = (__bridge NSString *)(MMCopyLoggedInAccount());
60 NSLog(@"notifyiCloudPreferencesAbout %@", eventName);
61
62 AEDesc aeDesc;
63 BOOL createdAEDesc = createAEDescWithAEActionAndAccountID((__bridge NSString *) kMMServiceIDKeychainSync, eventName, account, &aeDesc);
64 if (createdAEDesc) {
65 LSLaunchURLSpec lsSpec = {
66 .appURL = NULL,
67 .itemURLs = (__bridge CFArrayRef)([NSArray arrayWithObject: [NSURL fileURLWithPath:@"/System/Library/PreferencePanes/iCloudPref.prefPane"]]),
68 .passThruParams = &aeDesc,
69 .launchFlags = kLSLaunchDefaults | kLSLaunchAsync,
70 .asyncRefCon = NULL,
71 };
72 OSErr err = LSOpenFromURLSpec(&lsSpec, NULL);
73
74 if (err)
75 NSLog(@"Can't send event %@, err=%d", eventName, err);
76 AEDisposeDesc(&aeDesc);
77 } else {
78 NSLog(@"unable to create and send aedesc for account: '%@' and action: '%@'\n", account, eventName);
79 }
80 }
81
82
83 - (void) showiCloudPreferences
84 {
85 static NSAppleScript *script = nil;
86 if (!script) {
87 static NSString *script_src = @"tell application \"System Preferences\"\n"
88 "activate\n"
89 "set the current pane to pane id \"com.apple.preferences.icloud\"\n"
90 "end tell";
91 script = [[NSAppleScript alloc] initWithSource: script_src];
92 }
93
94 NSDictionary *scriptError = nil;
95 [script executeAndReturnError:&scriptError];
96
97 if (scriptError)
98 NSLog(@"scriptError: %@", scriptError);
99 else
100 NSLog(@"showiCloudPreferences success");
101 }
102
103
104 - (void) timerCheck
105 {
106 NSDate *nowish = [NSDate new];
107
108 self.state = [KNPersistentState loadFromStorage];
109 if ([nowish compare:self.state.pendingApplicationReminder] != NSOrderedAscending) {
110 NSLog(@"REMINDER TIME: %@ >>> %@", nowish, self.state.pendingApplicationReminder);
111
112 // self.circle.rawStatus might not be valid yet
113 if (SOSCCThisDeviceIsInCircle(NULL) == kSOSCCRequestPending) {
114 // Still have a request pending, send reminder, and also in addtion to the UI
115 // we need to send a notification for iCloud pref pane to pick up
116 CFNotificationCenterPostNotificationWithOptions(
117 CFNotificationCenterGetDistributedCenter(),
118 CFSTR("com.apple.security.secureobjectsync.pendingApplicationReminder"),
119 (__bridge const void *) [self.state.applicationDate description], NULL, 0
120 );
121
122 [self postApplicationReminder];
123 self.state.pendingApplicationReminder = [nowish dateByAddingTimeInterval:[self getPendingApplicationReminderInterval]];
124 [self.state writeToStorage];
125 }
126 }
127 }
128
129
130 - (void) scheduleActivityAt: (NSDate *) time
131 {
132 if ([time compare:[NSDate distantFuture]] != NSOrderedSame) {
133 NSTimeInterval howSoon = [time timeIntervalSinceNow];
134 if (howSoon > 0)
135 [self scheduleActivityIn:ceil(howSoon)];
136 else
137 [self timerCheck];
138 }
139 }
140
141
142 - (void) scheduleActivityIn: (int) alertInterval
143 {
144 xpc_object_t options = xpc_dictionary_create(NULL, NULL, 0);
145 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_DELAY, alertInterval);
146 xpc_dictionary_set_uint64(options, XPC_ACTIVITY_GRACE_PERIOD, XPC_ACTIVITY_INTERVAL_1_MIN);
147 xpc_dictionary_set_bool (options, XPC_ACTIVITY_REPEATING, false);
148 xpc_dictionary_set_bool (options, XPC_ACTIVITY_ALLOW_BATTERY, true);
149 xpc_dictionary_set_string(options, XPC_ACTIVITY_PRIORITY, XPC_ACTIVITY_PRIORITY_UTILITY);
150
151 xpc_activity_register(kLaunchLaterXPCName, options, ^(xpc_activity_t activity) {
152 [self timerCheck];
153 });
154 }
155
156
157 - (NSTimeInterval) getPendingApplicationReminderInterval
158 {
159 if (self.state.pendingApplicationReminderInterval)
160 return [self.state.pendingApplicationReminderInterval doubleValue];
161 else
162 return 24*60*60;
163 }
164
165
166 // Copied from sysdiagnose/src/utils.m
167 bool isAppleInternal(void)
168 {
169 static bool ret = false;
170 static dispatch_once_t onceToken;
171 dispatch_once(&onceToken, ^{
172 #if TARGET_OS_IPHONE
173 ret = CRIsAppleInternal();
174 #else
175 ret = CRHasBeenAppleInternalRecently();
176 #endif
177 });
178 return ret;
179 }
180
181
182 #define ICKC_EVENT_DISABLED "com.apple.security.secureobjectsync.disabled"
183 #define ICKC_EVENT_DEPARTURE_REASON "com.apple.security.secureobjectsync.departurereason"
184 #define ICKC_EVENT_NUM_PEERS "com.apple.security.secureobjectsync.numcircledevices"
185
186 - (void) applicationDidFinishLaunching: (NSNotification *) aNotification
187 {
188 appropriateNotificationCenter().delegate = self;
189 NSLog(@"Posted at launch: %@", appropriateNotificationCenter().deliveredNotifications);
190
191 self.viewedIds = [NSMutableSet new];
192 self.circle = [KDSecCircle new];
193 // self.state = [KNPersistentState loadFromStorage];
194 KNAppDelegate *me = self;
195
196 [self.circle addChangeCallback:^{
197 NSLog(@"{ChangeCallback}");
198 /* SOSCCStatus circleStatus = SOSCCThisDeviceIsInCircle(&error);
199 NSDate *nowish = [NSDate date];
200 PersistentState *state = [PersistentState loadFromStorage];
201 enum DepartureReason departureReason = SOSCCGetLastDepartureReason(&departError); */
202 // me.circle.rawStatus = SOSCCThisDeviceIsInCircle(&error);
203 NSDate *nowish = [NSDate date];
204 SOSCCStatus circleStatus = me.circle.rawStatus;
205 me.state = [KNPersistentState loadFromStorage];
206
207
208 // Pending application reminder
209 NSLog(@"{ChangeCallback} scheduleActivity %@", me.state.pendingApplicationReminder);
210 if (circleStatus == kSOSCCRequestPending)
211 [me scheduleActivityAt:me.state.pendingApplicationReminder];
212
213
214 // No longer in circle?
215 if ((me.state.lastCircleStatus == kSOSCCInCircle && (circleStatus == kSOSCCNotInCircle || circleStatus == kSOSCCCircleAbsent)) ||
216 (me.state.lastCircleStatus == kSOSCCCircleAbsent && circleStatus == kSOSCCNotInCircle && me.state.absentCircleWithNoReason) ||
217 me.state.debugLeftReason) {
218 enum DepartureReason reason = kSOSNeverLeftCircle;
219 if (me.state.debugLeftReason) {
220 reason = [me.state.debugLeftReason intValue];
221 me.state.debugLeftReason = nil;
222 [me.state writeToStorage];
223 } else {
224 CFErrorRef err = NULL;
225 reason = SOSCCGetLastDepartureReason(&err);
226 if (reason == kSOSDepartureReasonError) {
227 NSLog(@"SOSCCGetLastDepartureReason err: %@", err);
228 }
229 if (err) CFRelease(err);
230 }
231
232 if (reason != kSOSDepartureReasonError) {
233 // Post kick-out alert
234
235 // <rdar://problem/20862435> MessageTracer data to find out how many users were dropped & reset
236 msgtracer_domain_t domain = msgtracer_domain_new(ICKC_EVENT_DISABLED);
237 msgtracer_msg_t mt_msg = NULL;
238
239 if (domain != NULL)
240 mt_msg = msgtracer_msg_new(domain);
241
242 if (mt_msg) {
243 char s[16];
244
245 msgtracer_set(mt_msg, kMsgTracerKeySignature, ICKC_EVENT_DEPARTURE_REASON);
246 snprintf(s, sizeof(s), "%u", reason);
247 msgtracer_set(mt_msg, kMsgTracerKeyValue, s);
248
249 int64_t num_peers = 0;
250 CFArrayRef peerList = SOSCCCopyPeerPeerInfo(NULL);
251 if (peerList) {
252 num_peers = CFArrayGetCount(peerList);
253 if (num_peers > 99) {
254 // Round down # peers to 2 significant digits
255 int factor;
256 for (factor = 10; num_peers >= 100*factor; factor *= 10) ;
257 num_peers = (num_peers / factor) * factor;
258 }
259 CFRelease(peerList);
260 }
261 msgtracer_set(mt_msg, kMsgTracerKeySignature2, ICKC_EVENT_NUM_PEERS);
262 snprintf(s, sizeof(s), "%lld", num_peers);
263 msgtracer_set(mt_msg, kMsgTracerKeyValue2, s);
264
265 msgtracer_set(mt_msg, kMsgTracerKeySummarize, "NO");
266 msgtracer_log(mt_msg, ASL_LEVEL_DEBUG, "");
267 }
268
269 // FIXME:
270 // 1. Write here due to [me timerCheck] => [KNPersistentState loadFromStorage] below?!?
271 // 2. Or change call order of timerCheck, pendingApplication reminder below???
272 me.state.absentCircleWithNoReason = (circleStatus == kSOSCCCircleAbsent && reason == kSOSNeverLeftCircle);
273 [me.state writeToStorage];
274 NSLog(@"{ChangeCallback} departure reason %d", reason);
275
276 switch (reason) {
277 case kSOSDiscoveredRetirement:
278 case kSOSLostPrivateKey:
279 case kSOSWithdrewMembership:
280 case kSOSNeverAppliedToCircle:
281 break;
282
283 case kSOSNeverLeftCircle:
284 case kSOSMembershipRevoked:
285 case kSOSLeftUntrustedCircle:
286 default:
287 [me postKickedOutAlert: reason];
288 break;
289 }
290 }
291 }
292
293
294 // Circle applications: pending request(s) started / completed
295 if (me.circle.rawStatus != me.state.lastCircleStatus) {
296 SOSCCStatus lastCircleStatus = me.state.lastCircleStatus;
297 me.state.lastCircleStatus = circleStatus;
298
299 if (lastCircleStatus != kSOSCCRequestPending && circleStatus == kSOSCCRequestPending) {
300 NSLog(@"{ChangeCallback} Pending request START");
301 me.state.applicationDate = nowish;
302 me.state.pendingApplicationReminder = [me.state.applicationDate dateByAddingTimeInterval:[me getPendingApplicationReminderInterval]];
303 [me.state writeToStorage]; // FIXME: move below? might be needed for scheduleActivityAt...
304 [me scheduleActivityAt:me.state.pendingApplicationReminder];
305 }
306
307 if (lastCircleStatus == kSOSCCRequestPending && circleStatus != kSOSCCRequestPending) {
308 NSLog(@"Pending request completed");
309 me.state.applicationDate = [NSDate distantPast];
310 me.state.pendingApplicationReminder = [NSDate distantFuture];
311 [me.state writeToStorage];
312
313 // Remove reminders
314 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
315 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
316 if (note.userInfo[kValidOnlyOutOfCircleKey] && note.userInfo[@"ApplicationReminder"]) {
317 NSLog(@"{ChangeCallback} Removing notification %@", note);
318 [appropriateNotificationCenter() removeDeliveredNotification: note];
319 }
320 }
321 }
322
323 // [me.state writeToStorage];
324 }
325
326
327 // CircleJoinRequested
328 /* if (circleStatus != kSOSCCInCircle) {
329 if (circleStatus == kSOSCCRequestPending && currentAlert) { ... } */
330
331 // Clear out (old) reset notifications
332 if (me.circle.isInCircle) {
333 NSLog(@"{ChangeCallback} me.circle.isInCircle");
334 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
335 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
336 if (note.userInfo[kValidOnlyOutOfCircleKey]) {
337 NSLog(@"Removing existing notification (%@) now that we are in circle", note);
338 [appropriateNotificationCenter() removeDeliveredNotification: note];
339 }
340 }
341 }
342
343
344 // Applicants
345 NSLog(@"{ChangeCallback} Applicants");
346 NSMutableSet *applicantIds = [NSMutableSet new];
347 for (KDCirclePeer *applicant in me.circle.applicants) {
348 if (!me.circle.isInCircle) {
349 // Don't yammer on about circles we aren't in, and don't announce our own
350 // join requests as if the user could approve them locally!
351 break;
352 }
353 [me postForApplicant:applicant];
354 [applicantIds addObject:applicant.idString];
355 }
356
357
358 // Update notifications
359 NSUserNotificationCenter *notificationCenter = appropriateNotificationCenter();
360 NSLog(@"Checking validity of %lu notes", (unsigned long)notificationCenter.deliveredNotifications.count);
361 for (NSUserNotification *note in notificationCenter.deliveredNotifications) {
362 if (note.userInfo[@"applicantId"] && ![applicantIds containsObject:note.userInfo[@"applicantId"]]) {
363 NSLog(@"No longer an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
364 [notificationCenter removeDeliveredNotification:note];
365 } else {
366 NSLog(@"Still an applicant (%@) for %@ (I=%@)", note.userInfo[@"applicantId"], note, [note.userInfo compactDescription]);
367 }
368 }
369
370 me.state.lastCircleStatus = me.circle.rawStatus;
371
372 [me.state writeToStorage];
373 }];
374 }
375
376
377 - (BOOL) userNotificationCenter: (NSUserNotificationCenter *) center
378 shouldPresentNotification: (NSUserNotification *) notification
379 {
380 return YES;
381 }
382
383
384 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
385 didActivateNotification: (NSUserNotification *) notification
386 {
387 if (notification.activationType == NSUserNotificationActivationTypeActionButtonClicked) {
388 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Activate"]];
389 }
390 }
391
392
393 - (void) userNotificationCenter: (NSUserNotificationCenter *) center
394 didDismissAlert: (NSUserNotification *) notification
395 {
396 [self notifyiCloudPreferencesAbout:notification.userInfo[@"Dismiss"]];
397
398 // If we don't do anything here & another notification comes in we
399 // will repost the alert, which will be dumb.
400 id applicantId = notification.userInfo[@"applicantId"];
401 if (applicantId != nil) {
402 [self.viewedIds addObject:applicantId];
403 }
404 }
405
406
407 - (void) postForApplicant: (KDCirclePeer *) applicant
408 {
409 static int postCount = 0;
410
411 if ([self.viewedIds containsObject:applicant.idString]) {
412 NSLog(@"Already viewed %@, skipping", applicant);
413 return;
414 }
415
416 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
417 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
418 if ([applicant.idString isEqualToString:note.userInfo[@"applicantId"]]) {
419 if (note.isPresented) {
420 NSLog(@"Already posted&presented: %@ (I=%@)", note, note.userInfo);
421 return;
422 } else {
423 NSLog(@"Already posted, but not presented: %@ (I=%@)", note, note.userInfo);
424 }
425 }
426 }
427
428 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
429 // Contrary to HI spec (and I think it makes more sense)
430 // 1. otherButton == top : Not Now
431 // 2. actionButton == bottom: Continue
432 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
433 NSUserNotification *note = [NSUserNotification new];
434 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_TITLE_OSX);
435 note.informativeText = [NSString stringWithFormat: (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_APPROVAL_BODY_OSX), applicant.name];
436 note._displayStyle = _NSUserNotificationDisplayStyleAlert;
437 note._identityImage = [NSImage bundleImage];
438 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
439 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
440 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
441 note.identifier = [[NSUUID new] UUIDString];
442 note.userInfo = @{
443 @"applicantName": applicant.name,
444 @"applicantId" : applicant.idString,
445 @"Activate" : (__bridge NSString *) kMMPropertyKeychainAADetailsAEAction,
446 };
447
448 NSLog(@"About to post #%d/%lu (%@): %@", postCount, noteCenter.deliveredNotifications.count, applicant.idString, note);
449 [appropriateNotificationCenter() deliverNotification:note];
450 postCount++;
451 }
452
453
454 - (void) postKickedOutAlert: (int) reason
455 {
456 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
457 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
458 if (note.userInfo[kKickedOutKey]) {
459 if (note.isPresented) {
460 NSLog(@"Already posted&presented (removing): %@", note);
461 [appropriateNotificationCenter() removeDeliveredNotification: note];
462 } else {
463 NSLog(@"Already posted, but not presented: %@", note);
464 }
465 }
466 }
467
468 NSString *message = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_BODY_OSX);
469 if (isAppleInternal()) {
470 static const char *departureReasonStrings[] = {
471 "kSOSDepartureReasonError",
472 "kSOSNeverLeftCircle",
473 "kSOSWithdrewMembership",
474 "kSOSMembershipRevoked",
475 "kSOSLeftUntrustedCircle",
476 "kSOSNeverAppliedToCircle",
477 "kSOSDiscoveredRetirement",
478 "kSOSLostPrivateKey",
479 "unknown reason"
480 };
481 int idx = (kSOSDepartureReasonError <= reason && reason <= kSOSLostPrivateKey) ? reason : (kSOSLostPrivateKey + 1);
482 NSString *reason_str = [NSString stringWithFormat:(__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CR_REASON_INTERNAL), departureReasonStrings[idx]];
483 message = [message stringByAppendingString: reason_str];
484 }
485
486 // <rdar://problem/21988060> Improve wording of the iCloud keychain drop/reset error messages
487 // Contrary to HI spec (and I think it makes more sense)
488 // 1. otherButton == top : Not Now
489 // 2. actionButton == bottom: Continue
490 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
491 NSUserNotification *note = [NSUserNotification new];
492 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_PWD_REQUIRED_TITLE);
493 note.informativeText = message;
494 note._identityImage = [NSImage bundleImage];
495 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
496 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
497 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
498 note.identifier = [[NSUUID new] UUIDString];
499
500 note.userInfo = @{
501 kKickedOutKey : @1,
502 kValidOnlyOutOfCircleKey: @1,
503 @"Activate" : (__bridge NSString *) kMMPropertyKeychainMRDetailsAEAction,
504 };
505
506 NSLog(@"body=%@", note.informativeText);
507 NSLog(@"About to post #-/%lu (KICKOUT): %@", noteCenter.deliveredNotifications.count, note);
508 [appropriateNotificationCenter() deliverNotification:note];
509 }
510
511
512 - (void) postApplicationReminder
513 {
514 NSUserNotificationCenter *noteCenter = appropriateNotificationCenter();
515 for (NSUserNotification *note in noteCenter.deliveredNotifications) {
516 if (note.userInfo[@"ApplicationReminder"]) {
517 if (note.isPresented) {
518 NSLog(@"Already posted&presented (removing): %@", note);
519 [appropriateNotificationCenter() removeDeliveredNotification: note];
520 } else {
521 NSLog(@"Already posted, but not presented: %@", note);
522 }
523 }
524 }
525
526 // Contrary to HI spec (and I think it makes more sense)
527 // 1. otherButton == top : Not Now
528 // 2. actionButton == bottom: Continue
529 // 3. If we followed HI spec, replace "Activate" => "Dismiss" in note.userInfo below
530 NSUserNotification *note = [NSUserNotification new];
531 note.title = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_TITLE_OSX);
532 note.informativeText = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_REMINDER_BODY_OSX);
533 note._identityImage = [NSImage bundleImage];
534 note._identityImageStyle = _NSUserNotificationIdentityImageStyleRectangleNoBorder;
535 note.otherButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_NOT_NOW);
536 note.actionButtonTitle = (__bridge_transfer NSString *) SecCopyCKString(SEC_CK_CONTINUE);
537 note.identifier = [[NSUUID new] UUIDString];
538
539 note.userInfo = @{
540 @"ApplicationReminder" : @1,
541 kValidOnlyOutOfCircleKey: @1,
542 @"Activate" : (__bridge NSString *) kMMPropertyKeychainWADetailsAEAction,
543 };
544
545 NSLog(@"About to post #-/%lu (REMINDER): %@ (I=%@)", noteCenter.deliveredNotifications.count, note, [note.userInfo compactDescription]);
546 [appropriateNotificationCenter() deliverNotification:note];
547 }
548
549 @end